#include <stdbool.h>
#include "bolt.h"
#include "netnode.h"
#include "tp_debug.h"
#include "tap.h"
#include "pktio.h"

static struct netnode netnode[NETNODE_MAX];
static TAILQ_HEAD(nn_use, netnode) nn_use = TAILQ_HEAD_INITIALIZER(nn_use);


static void free_netnode(struct netnode *nn)
{
  nn->use = 0;
}

static struct netnode *alloc_netnode(int id)
{
  if (!valid_netnode_id(id))
    return NULL;
  struct netnode *nn = &netnode[id];
  if (nn->use)
    return NULL;
  memset(nn, 0, sizeof(struct netnode));
  nn->use = 1;
  nn->id = id;
  return nn;
}

struct netnode *netnode_get(int id)
{
  return (valid_netnode_id(id) && netnode[id].use) ? &netnode[id] : NULL;
}

static struct netnode *find_netnode(const char *name)
{
  struct netnode *lookup;
  TAILQ_FOREACH(lookup, &nn_use, entry) {
    if (strcmp(lookup->name, name) == 0)
      break;
  }
  return lookup;
} 

static bool create_netnode(int id, const char *name, int nn_type)
{
  struct netnode *nn = find_netnode(name);
  if (nn)
    return false;
  nn = alloc_netnode(id);
  if (!nn)
    return false;
  nn->pktio = -1;
  strncpy(nn->name, name, MAX_NAME_LEN - 1);
  execute_command ("brctl addbr %s", nn->name);
  nn_params_init(&nn->params, nn_type);
  nn->nn_type = nn_type;
  TAILQ_INSERT_TAIL(&nn_use, nn, entry);
  return true;
}

bool netnode_send(int id, struct llbuf *ll)
{
  if (!valid_netnode_id(id))
    return false;
  struct netnode *nn = &netnode[id];
  return nn->use && valid_pktio_id(nn->pktio)
    && pktio_send(nn->pktio, ll);
}

static bool netnode_add_pktio(int id, int pktio)
{
  if (!valid_netnode_id(id))
    return false;
  struct netnode *nn = &netnode[id];
  if (!nn->use || !valid_pktio_id(pktio))
    return false;
  struct pktio *p = get_pktio(pktio);
  LOG ("get pktio %p\n", p);
  if (p) {
    execute_command ("ip link set dev %s up", p->ifname);
    execute_command ("ip link set dev %s up", p->tap_name);
    execute_command ("brctl addif %s %s", nn->name, p->ifname);
    execute_command ("brctl addif %s %s", nn->name, p->tap_name);
    execute_command ("brctl stp %s off", nn->name);
    execute_command ("ifconfig %s up", nn->name);
    nn->pktio = pktio;
    return true;
  }
  return false;
}

static void _netnode_del_pktio(struct netnode *nn)
{
  if (!nn->use || !valid_pktio_id(nn->pktio))
    return;
  execute_command ("ifconfig %s down", nn->name);
  struct pktio *p = get_pktio(nn->pktio);
  if (p) {
    execute_command ("ifconfig %s down", p->ifname);
    execute_command ("brctl delif %s %s", nn->name, p->ifname);
    execute_command ("brctl delif %s %s", nn->name, p->tap_name);
    execute_command ("ifconfig %s up", p->ifname);
  }
  
  nn->pktio = -1;
}

static bool netnode_del_pktio(int id)
{
  if (!valid_netnode_id(id))
    return false;
  _netnode_del_pktio(&netnode[id]);
  return true;
}

static bool _remove_netnode(struct netnode *nn)
{
  _netnode_del_pktio(nn);

  if (nn->use) {
    execute_command ("brctl delbr %s", nn->name);
    TAILQ_REMOVE(&nn_use, nn, entry);
    free_netnode(nn);
    return true;
  } else
    return false;
}
  
static bool remove_netnode(int id)
{
  return valid_netnode_id(id) && _remove_netnode(&netnode[id]);
}

void netnode_deinit(void)
{
  while(!TAILQ_EMPTY(&nn_use)) {
    struct netnode *nn = TAILQ_FIRST(&nn_use);
    _remove_netnode(nn);
  }
}

void netnode_init(void)
{
  int i;
  for (i = 0; i < NETNODE_MAX; i++){
    netnode[i].id = i;
    free_netnode(&netnode[i]);
  }
}

static void netnode_show(void)
{
  struct netnode *lookup;
  printf ("%2s %16s %4s %5s\n", "id", "name", "type", "pktio");
  TAILQ_FOREACH(lookup, &nn_use, entry) {
    printf ("%2d %16s %4s %5d\n", lookup->id, lookup->name,
      lookup->nn_type == NN_TYPE_LAN ? "LAN" : "WAN", 
      lookup->pktio);
  }
}

BOLT(nn_pktio, "netnode <id> pktio add <pktio>")
{
  uint32_t id, pktio;

  const char *argz = pop_argz();
  if (!str2u32(argz, &pktio)) {
    printf ("invalid pktio\n");
    return;
  } 
  argz = pop_argz();
  if (!str2u32(argz, &id)) {
    printf("invalid id\n");
    return;
  }
  if (netnode_add_pktio(id, pktio)) 
    printf ("success\n");
  else
    printf ("no pktio create\n");
}

BOLT(nn_pktio_unset, "netnode <id> pktio unset")
{
  uint32_t id;

  const char *argz = pop_argz();
  if (!str2u32(argz, &id)) {
    printf("invalid id\n");
    return;
  }
  if (netnode_del_pktio(id)) 
    printf ("success\n");
  else
    printf ("no pktio was in use\n");
}

BOLT(nn_lan, "netnode <id> create lan <name>")
{
  uint32_t id;
  char name[MAX_NAME_LEN];
  
  const char *argz = pop_argz();
  if (argz == NULL || strlen(name) >= MAX_NAME_LEN){
    printf("invalid name\n");
    return;
  }
  strncpy(name, argz, MAX_NAME_LEN - 1);
  argz = pop_argz();
  if (!str2u32(argz, &id)) {
    printf("invalid id\n");
    return;
  }
  if (create_netnode(id, name, NN_TYPE_LAN))
    printf ("success\n");
  else
    printf ("failure\n");
}

BOLT(nn_wan, "netnode <id> create wan <name>")
{
  uint32_t id;
  char name[MAX_NAME_LEN];

  const char *argz = pop_argz();
  if (argz == NULL || strlen(name) >= MAX_NAME_LEN){
    printf("invalid name\n");
    return;
  }
  strncpy(name, argz, MAX_NAME_LEN - 1);
  argz = pop_argz();
  if (!str2u32(argz, &id) || !valid_netnode_id((int)id)) {
    printf("invalid id\n");
    return;
  }
  if (create_netnode(id, name, NN_TYPE_WAN))
    printf ("success\n");
  else
    printf ("failure\n");
}

BOLT(nn_release, "netnode <id> release")
{
  uint32_t id;

  const char *argz = pop_argz();
  if (!str2u32(argz, &id) || !valid_netnode_id((int)id)) {
    printf("invalid id\n");
    return;
  }
  if (remove_netnode(id))
    printf ("success\n");
  else
    printf ("no netnode was in use\n");
}

BOLT(nn_show, "netnode show")
{
  netnode_show();
}

BOLT_PROFILE(netnode, "pktio", netnode_profile, desc)
{
  struct netnode *nn;
  TAILQ_FOREACH(nn, &nn_use, entry) {
    cmd_load_profile(desc, "netnode %d create %s %s", 
      nn->id, nn->nn_type == NN_TYPE_WAN ? "wan" : "lan", nn->name);    
    if (nn->pktio >= 0) {
      cmd_load_profile(desc, "netnode %d pktio add %d", nn->id, nn->pktio);   
    }
  }
  cmd_load_profile(desc, "\n");
}
